package com.ansion.utils.permission;

import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.M;

import android.Manifest;
import android.annotation.TargetApi;
import android.app.Application;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;

import androidx.annotation.NonNull;

import com.ansion.log.VLog;
import com.ansion.utils.ResourceUtils;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.ObservableTransformer;
import io.reactivex.Single;
import io.reactivex.annotations.CheckReturnValue;
import io.reactivex.functions.Function;
import io.reactivex.subjects.PublishSubject;


/**
 * Real implementation of RxPermission that will show the usual Android dialog when requesting for permissions.
 */
public final class RealRxPermission implements RxPermission {
    static final Object TRIGGER = new Object();
    static RealRxPermission instance;

    static WeakReference<PermissionShadowActivity> shadowActivityWeakReference;

    static Handler handler = new Handler(Looper.getMainLooper());

    /**
     * @param context any context
     * @return a Singleton instance of this class
     */
    public static RealRxPermission getInstance(final Context context) {
        synchronized (RealRxPermission.class) {
            if (instance == null) {
                instance = new RealRxPermission((Application) context.getApplicationContext());
            }
        }

        return instance;
    }

    public static RealRxPermission getInstance() {
        if(instance == null){
            synchronized (RealRxPermission.class) {
                if (instance == null) {
                    instance = new RealRxPermission(ResourceUtils.getApplication());
                }
            }
        }
        return instance;
    }

    private final Application application;

    // Contains all the current permission requests. Once granted or denied, they are removed from it.
    private final Map<String, PublishSubject<Permission>> currentPermissionRequests = new HashMap<>();

    private RealRxPermission(final Application application) {
        this.application = application;
    }

    /**
     * Requests permissions immediately, <b>must be invoked during initialization phase of your application</b>.
     */
    @Override
    @NonNull
    public Observable<Permission> requestEach(String request, String guide, @NonNull final String... permissions) {
        return Observable.just(TRIGGER).compose(ensureEach(request,guide,false, false, permissions));
    }
    @NonNull
    public Observable<Permission> requestEach(Context context, String request, String guide, @NonNull final String... permissions) {
        return Observable.just(TRIGGER).compose(ensureEach(context,request,guide,false, false, false, permissions));
    }
    @NonNull
    public Observable<Permission> requestEach(Context context, String request, String guide,boolean forceDialog, boolean forceNoDialog, @NonNull final String... permissions) {
        return Observable.just(TRIGGER).compose(ensureEach(context,request,guide,forceDialog, false, forceNoDialog, permissions));
    }
    @Override
    public Observable<Permission> requestEach(String request, String guide, boolean forceDialog, String... permissions) {
        return Observable.just(TRIGGER).compose(ensureEach(request,guide,forceDialog, false, permissions));
    }

    public Single<Permission> requestPrime(String request, String guide, String permission) {
        return Observable.just(TRIGGER).compose(ensureEach(request,guide,false, true, permission)).firstOrError();
    }

    public Observable<Permission> requestPrimeEach(String request, String guide, String... permissions) {
        return Observable.just(TRIGGER).compose(ensureEach(request,guide,false, true, permissions));
    }

    /**
     * Requests the permission immediately, <b>must be invoked during initialization phase of your application</b>.
     */
    @NonNull
    public Single<Permission> request(String request, String guide, @NonNull final String permission) {
        return requestEach(request,guide, permission).firstOrError();
    }

    /**
     * Requests the permission immediately, <b>must be invoked during initialization phase of your application</b>.
     */
    @NonNull
    public Single<Permission> request(Context context, String request, String guide, @NonNull final String permission) {
        return requestEach(context,request,guide, permission).firstOrError();
    }

    /**
     * Map emitted items from the source observable into {@link Permission} objects for each
     * permission in parameters.
     * <p>
     * If one or several permissions have never been requested, invoke the related framework method
     * to ask the user if he allows the permissions.
     */
    @NonNull
    @CheckReturnValue
    private <T> ObservableTransformer<T, Permission> ensureEach(final String request, final String guide, final boolean forceDialog, final boolean primeStyle, @NonNull final String... permissions) {

        final RequestData requestData = new RequestData();
        requestData.request = request;
        requestData.guide = guide;
        requestData.forceDialog = forceDialog;
        requestData.permissions = permissions;
        requestData.primeStyle = primeStyle;

        return new ObservableTransformer<T, Permission>() {
            @Override
            @NonNull
            @CheckReturnValue
            public ObservableSource<Permission> apply(final Observable<T> o) {
                return request(o,requestData);
            }
        };
    }

    @NonNull
    @CheckReturnValue
    private <T> ObservableTransformer<T, Permission> ensureEach(final Context context, final String request, final String guide, final boolean forceDialog, final boolean primeStyle, boolean forceNoDialog, @NonNull final String... permissions) {

        final RequestData requestData = new RequestData();
        requestData.request = request;
        requestData.guide = guide;
        requestData.forceDialog = forceDialog;
        requestData.permissions = permissions;
        requestData.primeStyle = primeStyle;
        requestData.context = context;
        requestData.forceNoDialog = forceNoDialog;

        return new ObservableTransformer<T, Permission>() {
            @Override
            @NonNull
            @CheckReturnValue
            public ObservableSource<Permission> apply(final Observable<T> o) {
                return request(o,requestData);
            }
        };
    }

    @NonNull
    @CheckReturnValue
    @SuppressWarnings("checkstyle:overloadmethodsdeclarationorder")
    Observable<Permission>
    request(final Observable<?> trigger, final RequestData requestData) {
        return Observable.merge(trigger, pending(requestData.permissions))
                .flatMap(new Function<Object, Observable<Permission>>() {
                    @Override
                    @NonNull
                    @CheckReturnValue
                    public Observable<Permission> apply(final Object o) {
                        return requestOnM(requestData);
                    }
                });
    }

    @NonNull
    @CheckReturnValue
    private Observable<?> pending(@NonNull final String... permissions) {
        for (final String p : permissions) {
            if (!currentPermissionRequests.containsKey(p)) {
                return Observable.empty();
            }
        }

        return Observable.just(TRIGGER);
    }
    public void closeUnClosedPermission() {
        if (shadowActivityWeakReference != null && shadowActivityWeakReference.get() != null && !shadowActivityWeakReference.get().isDestroyed()) {
            shadowActivityWeakReference.get().notNow();
//            shadowActivityWeakReference.get().finish();
        }
    }
    @NonNull
    @CheckReturnValue
    @TargetApi(M)
    Observable<Permission> requestOnM(RequestData requestData) {
        final List<Observable<Permission>> list = new ArrayList<>(requestData.permissions.length);
        final List<String> unrequestedPermissions = new ArrayList<>();

        if (shadowActivityWeakReference != null && shadowActivityWeakReference.get() != null && !shadowActivityWeakReference.get().isDestroyed() && !shadowActivityWeakReference.get().isFinishing()) {
            shadowActivityWeakReference.get().notNow();
            shadowActivityWeakReference.get().finish();
        }

        currentPermissionRequests.clear();

        for (final String permission : requestData.permissions) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
                    && permission.equals(Manifest.permission.READ_MEDIA_IMAGES)
                    && (isGranted(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) || isGranted(Manifest.permission.READ_MEDIA_IMAGES))
            ) {
                if (VLog.OPEN_LOG) {
                    VLog.d("TAG_PERMISSION_CHECK", " RealRxPermission permission is %s >> READ_MEDIA_VISUAL_USER_SELECTED isGranted = %s, READ_MEDIA_IMAGES isGranted = %s", permission, isGranted(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED), isGranted(Manifest.permission.READ_MEDIA_IMAGES));
                }
                list.add(Observable.just(Permission.granted(permission)));
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
                    && permission.equals(Manifest.permission.READ_MEDIA_VIDEO)
                    && (isGranted(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) || isGranted(Manifest.permission.READ_MEDIA_VIDEO))
            ) {
                if (VLog.OPEN_LOG) {
                    VLog.d("TAG_PERMISSION_CHECK", " RealRxPermission permission is %s >> READ_MEDIA_VISUAL_USER_SELECTED isGranted = %s, READ_MEDIA_VIDEO isGranted = %s", permission, isGranted(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED), isGranted(Manifest.permission.READ_MEDIA_VIDEO));
                }
                list.add(Observable.just(Permission.granted(permission)));
            } else if (isGranted(permission)) {
                list.add(Observable.just(Permission.granted(permission)));
            } else if (isRevokedByPolicy(permission)) {
                list.add(Observable.just(Permission.revokedByPolicy(permission)));
            } else {
                PublishSubject<Permission> subject = currentPermissionRequests.get(permission);

                // Create a new subject if not exists
                if (subject == null) {
                    unrequestedPermissions.add(permission);
                    subject = PublishSubject.create();
                    currentPermissionRequests.put(permission, subject);
                }

                list.add(subject);
            }
        }

        if (!unrequestedPermissions.isEmpty()) {
            final String[] permissionsToRequest = unrequestedPermissions.toArray(new String[0]);
            requestData.permissions = permissionsToRequest;
            startShadowActivity(requestData);
        }

        return Observable.concat(Observable.fromIterable(list));
    }

    /**
     * Returns true if the permission is already granted.
     * <p>
     * Always true if SDK &lt; 23.
     */
    @Override
    @CheckReturnValue
    public boolean isGranted(@NonNull final String permission) {
        return !isMarshmallow() || isGrantedOnM(permission);
    }

    /**
     * Returns true if the permission has been revoked by a policy.
     * <p>
     * Always false if SDK &lt; 23.
     */
    @Override
    @CheckReturnValue
    public boolean isRevokedByPolicy(@NonNull final String permission) {
        return isMarshmallow() && isRevokedOnM(permission);
    }

    @TargetApi(M)
    private boolean isGrantedOnM(final String permission) {
        return application.checkSelfPermission(permission) == PERMISSION_GRANTED;
    }

    /**
     * Returns false if user checked "Deny and Don't Ask Again" box
     * @param permission
     * @return
     */
    @TargetApi(M)
    private boolean shouldShowRationale(final String permission) {
        if (shadowActivityWeakReference.get() != null && !shadowActivityWeakReference.get().isDestroyed()) {
            return shadowActivityWeakReference.get().shouldShowRequestPermissionRationale(permission);
        }
        return true;
    }

    @TargetApi(M)
    private boolean isRevokedOnM(final String permission) {
        return application.getPackageManager().isPermissionRevokedByPolicy(permission, application.getPackageName());
    }

    void startShadowActivity(RequestData requestData) {
        PermissionShadowActivity.start(
                requestData.context != null ? requestData.context : application,
                requestData.request,
                requestData.guide,
                requestData.forceDialog,
                requestData.permissions,
                requestData.primeStyle,
                requestData.forceNoDialog);
    }

    void onRequestPermissionsResult(@NonNull final int[] grantResults, @NonNull final boolean[] rationale, @NonNull final boolean[] rationaleAfter, @NonNull final String... permissions) {

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                final int size = permissions.length;

                for (int i = 0; i < size; i++) {
                    final PublishSubject<Permission> subject = currentPermissionRequests.get(permissions[i]);

                    if (subject == null) {
                        continue;
                    }

                    currentPermissionRequests.remove(permissions[i]);

                    final boolean granted = grantResults[i] == PERMISSION_GRANTED;

                    if (granted) {
                        subject.onNext(Permission.granted(permissions[i]));
                        PermissionSharedPref.getInstance(application).granted(permissions[i]);
                    } else if (!rationaleAfter[i]) {
                        subject.onNext(Permission.deniedNotShown(permissions[i]));
                        PermissionSharedPref.getInstance(application).deniedNotShow(permissions[i]);
                    } else {
                        subject.onNext(Permission.denied(permissions[i]));
                    }

                    subject.onComplete();
                }
            }
        },10);

    }

    void onRequestPermissionsResult(@NonNull final String... permissions) {
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                final int size = permissions.length;

                for (int i = 0; i < size; i++) {
                    final PublishSubject<Permission> subject = currentPermissionRequests.get(permissions[i]);

                    if (subject == null) {
                        continue;
                    }

                    currentPermissionRequests.remove(permissions[i]);

                    if (isGranted(permissions[i])) {
                        subject.onNext(Permission.granted(permissions[i]));
                        PermissionSharedPref.getInstance(application).granted(permissions[i]);
                    } else {
                        subject.onNext(Permission.denied(permissions[i]));
                    }

                    subject.onComplete();
                }
                currentPermissionRequests.clear();
            }
        },10);

    }

    boolean isMarshmallow() {
        return SDK_INT >= M;
    }


    private static class RequestData {
        String request;
        String guide;
        boolean primeStyle;
        boolean forceDialog;
        String[] permissions;
        Context context;
        boolean forceNoDialog;
    }
}
